{
	// 加载模块
	let utils = (typeof(global) === "undefined") ? window.utils : global.utils;
	// 需要的 Paho.MQTT.javascript 实现
	// 文档见：https://www.eclipse.org/paho/files/jsdoc/index.html
	let Paho = (typeof(global) === "undefined") ? window.Paho : global.Paho;
	let wskt = (typeof(global) === "undefined") ? window.wskt : global.wskt;
	let webHost = (typeof(global) === "undefined") ? window.webHost : global.webHost;

	const MSG_PING = 0;
	const MSG_PING_ACK = 1;
	const MSG_CALL = 10;
	const MSG_RETURN = 20;
	const MSG_NOTIFY = 30;

	function getRandomInt() {
		return Math.round(Math.random() * 100000000);
	};

	class JsonRpc {
		/**
		 * 构造函数
		 * @param address 目前的协议地址格式如下：
		 * <li>JsonRpc@MQTT@WebSocket 地址格式，由3部分组成：[承载协议 mqtt] @ [客户端监听主题前缀 clientTopic] # [mqttBroker 连接地址]
         *                                          mqtt@ehome/gateway4/mdm/up/#ws(s)://ehome.szwistar.com/mqtt/
		 * <li>JsonRpc@WebSocket 地址格式：			wskt@ws(s)://localhost:8504/webskt
		 * <li>JsonRpc@WebHost 地址格式(仅内置Web)： webhost@webHost
		 * <li>JsonRpc@HTTP 地址格式：				http@http
		 * @param clientId 客户端ID：如果为 null，则默认使用一个随机数
		 * @param pingTarget ping() 的目标，用于在 ping() 时将消息发向指定目标；如果为 null，则自动使用最后一次 send()/notify() 时使用的 target
		 */
		constructor(address, clientId, pingTarget) {
			// 检查地址有效性
			if(utils.empty(address) || (typeof address) != "string") { throw {code: 400, message: "JsonRpc 地址无效！"}; }
			// 保存地址
			this.address = address;
			
			// 通信协议所使用的通信连接对象
			// 不同的协议通信连接对象类型可能是不同的：
			// JsonRpc@MQTT: paho.mqtt 对象
			// JsonRpc@WebSocket: WebSocket 连接
			// JsonRpc@WebHost: 浏览器内置 webHost 接口
			// JsonRpc@HTTP: XMLHttpRequest 对象
			this.theLink = null;
			// 客户端ID
			this.clientId = (clientId != null) ? clientId : getRandomInt().toString();
			// 默认的 RPC 超时时间(ms)
			this.rpcTimeout = 10000;
			// ping() 的目标
			this.pingTarget = pingTarget;
			// 同步定时器
			this.keepAliveTimer = null;

			// RPC 异步响应回调表
			this.aysncResponse = {};
			// 消息通知回调表
			this.notifyHandler = {};
		}

		/**
		 * 实例必须实现的接口：发送消息
		 * @param {Object} target 消息要发送的目标地址
		 * @param {Object} message 消息
		 * @param {Object} qos 消息传输 QOS（服务质量）
		 */
		send(target, message, qos) { throw "未实现!"; }
		
		/**
		 * 实例必须实现的接口：关闭连接
		 */ 
		close() { throw "未实现!"; }
		
		/**
		 * 增强的订阅/取消订阅接口
		 * <p>可以同时 订阅/取消订阅 多个消息过滤器
		 * @param {String[]} addFilters 要订阅的消息过滤器列表
		 * @param {String[]} delFilters 要取消订阅的消息过滤器列表
		 * @param {String} token 鉴权 Token，如果对端有鉴权机制，如果客户端没有授权，可能禁止订阅某些消息
		 * @param {Function} callback 操作完成（成功或失败）或超时，都会调用此回调函数：
		 * 	callback({
		 * 		errCode: {Number} 错误码，如果错误码为 0，表示成功；否则表示失败，错误消息在 body 中
		 * 		body: 如果错误码不为 0，错误消息；其它情况未定义
		 * 	})
		 * @param {Number} timeout 超时时间
		 */
		subscribeEx(addFilters, delFilters, token, callback, timeout) { throw "未实现!"; }

		// 实例可以重载的接口：是否已连接
		isConnected() { return this.theLink != null; }
		
		/**
		 * 实例可以重载的事件处理接口：当连接建立时
		 */ 
		onOpen() { console.log("连接已建立！"); }
		
		/**
		 * 实例可以重载的事件处理接口：当连接关闭时
		 */ 
		onClose(error) { console.log("连接已断开！" + JSON.stringify(error)); }
		
		/**
		 * 实例可以重载的事件处理接口：当遇到错误时
		 * @param {Object} error 标准错误信息 {
		 *	{Number} errCode: 错误码；如果无错误，则错误码为 0
		 *	{String} body: 错误信息；错误码为 0 时，未定义
		 * }
		 */
		onError(error) { console.log(JSON.stringify(error)); }

		// 接收消息处理
		onMessage(message) {
			let me = this;

			// 重新计时
			me.resetKeepAliveTimer();

			// 消息解码
			let msg = JSON.parse(message);

			if (msg.msgType == MSG_RETURN) {
				// 查找 aysncResponse 并回调结果
				if (me.aysncResponse.hasOwnProperty(msg.msgId)) {
					// 取出并删除回调对象
					let pret = me.aysncResponse[msg.msgId]; delete me.aysncResponse[msg.msgId];
					// 回调处理函数
					if (pret && pret.callback) {
						// 取消超时定时器
						if (pret.timer) { clearTimeout(pret.timer); }
						// 打印调用耗时时间
						// console.log("RPC 调用耗时(ms): " + (new Date().getTime() - pret.timestamp));						
						// 调用回调函数处理
						pret.callback(msg);
					}
				} else {
					console.log("未知来源的JsonRpc消息: " + message);
				}
			} else if (msg.msgType == MSG_NOTIFY) {
				// 查找 notifyHandler 并回调结果
				// if (me.notifyHandler.hasOwnProperty(msg.method)) {
				// 取出回调对象
				let callback = me.notifyHandler[msg.method];
				// 调用回调函数处理
				if (callback) {
					callback(msg);
				}
				// }
			} else if (msg.msgType == MSG_PING) {
				let retMsg = {
					msgType: MSG_PING_ACK,
					msgId: msg.msgId,
					ms: msg.ms,
				}

				// 发回消息
				me.send(JSON.stringify(retMsg))
			} else if (msg.msgType == MSG_PING_ACK) {
				let delay = new Date().getTime() - msg.ms;
				// console.log("ping ack delay(ms): " + delay)
			} else {
				console.log("不支持的消息: message");
			}
		};

		/**
		 * 发起 RPC 调用
		 * @param {String} target 目标主题(也就是要调用的对端的监听主题)
		 * @param {String} token 鉴权 Token
		 * @param {String} method 要调用的方法
		 * @param {Object} params 调用参数
		 * @param {Function} callback 调用结果回调函数，当调用成功/失败/超时，都会回调这个函数: callback(message)，其中 message 就是调用返回结果(标准返回消息)
		 * @param {Number} timeout 超时时间(ms)
		 */
		rpc(target, token, method, params, callback, timeout) {
			let me = this;
			
			// 创建消息
			let msg = {
				msgType: MSG_CALL,
				msgId: getRandomInt(),
				from: me.getClientId(), // !!! 注意：此处不要直接使用 me.clientId，因为不同的协议可能不同 !!!
				token: token,
				method: method,
				params: JSON.stringify(params),
			}

			// 如果未提供超时时间，则使用默认的超时时间
			if (!timeout) { timeout = this.rpcTimeout; }
			
			// 设定超时定时器
			let timer = setTimeout(function() {
				// 根据消息ID，取出并删除回调对象
				let pret = me.aysncResponse[msg.msgId];
				delete me.aysncResponse[msg.msgId];
				
				// 超时回调
				if (pret && pret.callback) {
					pret.callback({
						msgType: MSG_RETURN,
						msgId: msg.msgId,
						errCode: 400,
						body: "timeout",
					});
				}
			}, timeout);
			
			// 记录当前时间戳，以便计算调用耗时时间
			let timestamp = new Date().getTime();

			// 把异步回调接口放到异步响应表中
			me.aysncResponse[msg.msgId] = {
				callback: callback,
				timer: timer,
				timestamp: timestamp
			}
			
			// 发送消息
			me.send(target, JSON.stringify(msg), 1);
		};

		/**
		 * 发送消息通知
		 * @param {String} target 目标主题(也就是发布消息主题)
		 * @param {Object} token 鉴权 Token，用于表明自己的身份；接收端可以通过验证这个 token 来确认这个消息的来源是否可信，从而判断是否要处理这个消息
		 * @param {Object} method 事件处理器名称（也相当于事件的类型名称）；接收端收到此消息时，如果注册了相同名称的事件处理器，则调用它处理此消息
		 * @param {Object} params 消息参数
		 */
		notify(target, token, method, params) {
			let me = this;
			let msg = {
				msgType: MSG_NOTIFY,
				msgId: getRandomInt(),
				from: me.getClientId(),
				token: token,
				method: method,
				params: JSON.stringify(params),
			}

			me.send(target, JSON.stringify(msg), 0);
		};

		/**
		 * 注册事件通知处理器
		 * @param {String} method 本端事件处理器名称（也相当于事件的类型名称）；
		 *		当接收到对端使用 notify(target, token, method, params) 发送的消息通知的时候，
		 *		如果其中的 method 与本端注册的 method 相同，则对应的回调函数就会被调用。
		 * @param {Function} callback 回调函数： callback(message)，其中 message 为对端发送的通知消息，具体格式参见 notify() 函数
		 */ 
		registerNotifyHandler(method, callback) {
			this.notifyHandler[method] = callback;
		}
		
		/**
		 * 获取客户端ID(发送消息时需要以此函数返回值标明消息来源)
		 */
		getClientId() { return this.clientId; }

		/**
		 * 设置 rpc() 函数默认超时时间
		 * @param {Number} timeout 超时时间(ms)
		 */ 
		setRpcTimeout(timeout) { this.rpcTimeout = timeout; }
		
		/**
		 * 获取承载实例通信的连接对象
		 * @return {Object} 每种协议实现返回的对象可能不同，具体见各个协议的 connect() 函数
		 */ 
		getLink() { return this.theLink; }
		
		/**
		 * 获取连接时使用的 URL 地址
		 * <p>这个地址可以用于:
		 * <li>重用本 JsonRpc 连接前，判断地址是否相同，如果地址不相同，不可以重用！
		 * <li>连接断后，重连时，可能需要此地址
		 */
		getaddress() { return this.address; }

		// PING
		ping(target) {
			let me = this;
			let msg = {
				msgType: MSG_PING,
				msgId: getRandomInt(),
				from: me.getClientId(),
				ms: new Date().getTime(),
			}

			me.send(target, JSON.stringify(msg), 0);
		};

		// 心跳定时器重新计时
		resetKeepAliveTimer(target) {
			let me = this;

			// 如果没有设置 ping 的目标，则不用保持心跳
			if(target) { me.pingTarget = target; }
			if(me.pingTarget == null) { return; }

			// 重新设置定时
			if(me.keepAliveTimer) { clearInterval(me.keepAliveTimer); }
			me.keepAliveTimer = setInterval( function() { me.ping(me.pingTarget); }, 10000 );
		};

		/**
		 * ============================================================================================================
		 * JsonRpc@MQTT 实现
		 * ============================================================================================================
		 */
		// 初始化新的 MQTT 连接(如果是已经初始化过的，则不需要重复调用)
		_initNewMqttLink() {
			let me = this;
		
			// 启动定时 ping()
			me.resetKeepAliveTimer();
			
			// 因为 MQTT 对象断开之后，可以重新连接，不用新建，所以这里只需要对 me.theLink 挂接一次回调函数即可 ！
			
			// 挂接回调函数：当连接中断时
			// onConnectionLost({errorCode:123, errorMessage:"xxx"})
			me.theLink.onConnectionLost = function(params) {
				// 回调实例方法
				me.onClose({errCode: params.errorCode, body: params.errorMessage});
				// 停止定时 ping()
				clearInterval(me.keepAliveTimer);
			};
		
			// 挂接回调函数：当收到消息时
			me.theLink.onMessageArrived = function(message) {
				// 回调实例方法
				me.onMessage(message.payloadString);
			};
		
			// 挂接回调函数：当消息发送完成时
			me.theLink.onMessageDelivered = function(message) {
				// do nothing
			};

			// 重载实例的 getClientId()
			// !!! 注意：JsonRpc@MQTT 要以 'clientTopic + clientId' 做为消息源 !!!
			me.getClientId = function() {
				return me.clientTopic + me.clientId;
			};

			// 重载实例的 isConnected()
			me.isConnected = function() {
				return me.theLink && me.theLink.isConnected();
			};

			// 实现实例的 send()
			me.send = function(target, message, qos) {
				if(!me.isConnected()) {
					console.log("连接中断，无法发送消息")
					return;
				}
				
				// 心跳定时器重新计时(同时将消息的目标地设置为 ping() 的默认目标)
				me.resetKeepAliveTimer(target);
				
				// 创建 MQTT 消息
				message = new Paho.MQTT.Message(message);
				message.destinationName = target;
				message.qos = qos;
				
				// 原始 send(topic, payload, qos, retained)
				// return me.theLink.send(target, message, qos, false);
				// 调用连接原生的 send() 发送消息
				return me.theLink.send(message);
			};

			// 实现实例的 close()
			me.close = function() {
				// 因为 MQTT 对象断开之后，可以重新连接，不用新建，所以这里不要把 me.theLink 置空 ！
				return me.theLink.disconnect();
			};
			
			/**
			 * Paho.MQTT.Client.subscribe(filter, subscribeOptions) 参数说明
			 * @param {String} filter 要订阅的主题过滤描述符
			 * @param {Object} subscribeOptions 订阅参数 {
			 *	{Object} invocationContext 回调时原样传给 onSuccess/onFailure
			 *	{Function} onSuccess 成功时回调 onSuccess({invocationContext: invocationContext})
			 *	{Function} onFailure 失败时回调 onFailure({invocationContext: invocationContext, errorCode: number, errorMessage: string})
			 *	{number} timeout 超时时间(ms)
			 * }
			 */
			// 封装 MQTT 的 subscribe() 为异步函数
			let _subscribe = async function(topicFilter, timeout) {			
				return new Promise( async (resolve, reject) => {
					let onSuccess = function(params) { resolve({errCode: 0}); }
					let onFailure = function(params) { resolve({errCode: params.errorCode, body: params.errorMessage}); }
					return me.theLink.subscribe(topicFilter, { onSuccess: onSuccess, onFailure: onFailure, timeout: timeout });
				});
			};
			
			// 封装 MQTT 的 unsubscribe() 为异步函数
			let _unsubscribe = async function(topicFilter, timeout) {			
				return new Promise( async (resolve, reject) => {
					let onSuccess = function(params) { resolve({errCode: 0}); }
					let onFailure = function(params) { resolve({errCode: params.errorCode, body: params.errorMessage}); }
					return me.theLink.unsubscribe(topicFilter, { onSuccess: onSuccess, onFailure: onFailure, timeout: timeout });
				});
			};
			
			// 实现实例的 subscribeEx()
			me.subscribeEx = async function(addFilters, delFilters, token, callback, timeout) {
				if(!timeout) { timeout = me.rpcTimeout; }
				
				// 订阅多个主题
				for(let i in addFilters) {
					// 调用封装的异步函数，并等待返回
					let ret = await _subscribe(addFilters[i], timeout);
					// 如果有错误，就回调并返回
					if(ret.errCode != 0 && callback) { callback(ret); return; }
				}
				
				// 取消订阅多个主题
				for(let i in delFilters) {
					// 调用封装的异步函数，并等待返回
					let ret = await _unsubscribe(delFilters[i], timeout);
					// 如果有错误，就回调并返回
					if(ret.errCode != 0 && callback) { callback(ret); return; }
				}
				
				// 成功完成，回调
				if(callback) { callback({errCode: 0}); }
			};
		}
	
		/**
		 * 通过 MQTT 连接到服务端
		 * @param {Object} url 由两部分组成："$(clientTopic)#$(brokerUrl)"
		 * <li>brokerUrl 是 MQTT 服务器地址（使用 WebSocket 协议承载），如：ws(s)://ehome.szwistar.com/mqtt/
		 * <li>clientTopic 是本机所默认监听的主题，用于接收 "rpc 返回消息" 和 "对端发来的 notify 消息"
		 */
		_connectMqtt(url) {
			let me = this;
			
			// 是否需要新建通信连接：如果连接对象已经存在，则直接使用当前的连接对象；否则要新建一个
			let isNew = (me.theLink == null);
			
			if(isNew) {
				// 新建一个通信连接
				// 从地址中提取 clientTopic, brokerUrl
				let [clientTopic, brokerUrl] = url.split("#");				
				// 保存 clientTopic 到实例
				me.clientTopic = clientTopic;

				// 创建 MQTT Client
				me.theLink = new Paho.MQTT.Client(brokerUrl, me.clientId);
			}

			// 连接 MQTT Client
			me.theLink.connect({
				// 连接超时时间(s)，默认是30s
				timeout: 10,
				// 多长时间未通信，则断开连接，默认是60s
				keepAliveInterval: 5 * 60,
				// 用户名
				//userName: null,
				// 密码
				//password: null,

				// 成功回调：初始化实例
				// onSuccess({invocationContext:{...}});
				onSuccess: function() {
					// 初始化新的 MQTT 连接(如果是已经初始化过的，则不需要重复调用)
					if(isNew) { me._initNewMqttLink(); }
			
					// 连接成功后，要重新订阅接收消息主题(即以自己的消息源做为接收消息主题)
					me.theLink.subscribe(me.getClientId());
					console.log("JsonRpc@MQTT 默认监听主题：" + me.getClientId());
					
					// 回调实例方法
					me.onOpen();
				},

				// 失败回调
				// onFailure({invocationContext:{...}, errorCode:123, errorMessage:"xxx"});
				onFailure: function(params) {
					// 回调实例方法
					console.log("连接失败：" + params.errorMessage)
					me.onError({errCode: params.errorCode, body: params.errorMessage});
				},
			});
		}

		/** 
		 * ============================================================================================================
		 * JsonRpc@WebSocket 实现
		 * ============================================================================================================
		*/
		// 初始化实例
		_initWebSocketInstance() {
			let me = this;

			// 实现实例的 send()
			me.send = function(target, message, qos) {
				if(!me.isConnected()) {
					console.log("连接中断，无法发送消息")
					return;
				}
				
				// 心跳定时器重新计时
				me.resetKeepAliveTimer(target);
				
				// 原始 send(message)
				me.theLink.send(message);
			};
			
			// 实现实例的 subscribeEx()
			me.subscribeEx = function(addFilters, delFilters, token, callback, timeout) {
				me.rpc(null, token, "_subscribe", {add: addFilters, del: delFilters}, callback, timeout);
			};

			// 实现实例的 close()
			me.close = function() {
				// 调用原始连接的 close()
				me.theLink.close();
				me.theLink = null;
			};
		}

		/**
		 * 通过 WebSocket 连接到服务端
		 * @param {String} url 服务端所在的 WebSocket 地址，如：ws(s)://localhost:8504/webskt
		 */
		_connectWebSocket(url) {
			let me = this;
			
			// 因为重连只能通过新建 WebSocket 实现，所以如果连接对象已经存在，则要关闭之前的连接
			//if(me.theLink) { me.close(); }
			// 如果连接已经建立，则直接返回
			if(me.theLink) { return; }
			
			// 因为在 wskt.open() 之前无法先创建 WebSocket 连接，所以需要一个 closure 来传递 WebSocket 连接到挂接的回调函数中去
			let closure = {};

			// 打开 WebSocket，同时挂接回调函数 onOpen()/onClose()/onMessage()/onError()
			closure.aLink = wskt.open(url, {
				onOpen: function(params) {
					// 第一次需要初始化实例
					if(me.theLink == null) { me._initWebSocketInstance(); }
					// 只有当打开成功时，才能保存连接到实例！
					me.theLink = closure.aLink;
					// 启动心跳定时器: 因为 WebSocket 属于 P2P 连接，对端的 target 可以是任意非空值(否则不会定时 ping)，只要给一个不为空的 pingTarget 就行
					me.resetKeepAliveTimer("dummy");
					// 回调实例方法
					me.onOpen();
				},

				onClose: function(params) {
					// 回调实例方法
					me.onClose({errCode: 500, body:params});
					// 停止定时 ping()
					clearInterval(me.keepAliveTimer);
					// 因为重连只能通过新建 WebSocket 实现，所以连接断开后，实例连接必须要置空！
					me.theLink = null;
				},

				onMessage: function(params) {
					// 回调实例方法
					me.onMessage(params.data);
				},

				onError: function(params) {
					// 回调实例方法
					me.onError({errCode: 500, body: "WebSocket 错误！"});
				}
			});
		}

		/** 
		 * ============================================================================================================
		 * JsonRpc@WebHost 实现
		 * ============================================================================================================
		*/	   
		/**
		 * JsonRpc@WebHost
		 * 连接到内置 WebView
		 * @param {String} url 实际上是内置 WebView 暴露的 WebHost 接口对象名称
		 */
		_connectWebHost(url) {
			let me = this;
			
			// 如果连接对象已经存在，则直接返回即可
			if(me.theLink) { me.onOpen(); return; }

			// 判断浏览器是否有指定的 WebHost 接口
			let webHost = window[url];
			if(webHost == null || !webHost.isReady()) {
				me.onError({errCode: 500, body: "内置浏览器接口不存在或未准备好！"});
				return;
			}
			// 绑定为全局默认的 JsonRpc@WebHost 回调对象
			window.webHostJsonRpcListener = me;
			webHost.listenOn(null, "webHostJsonRpcListener");

			// 实现实例的 send()
			me.send = function(target, message, qos) {
				// 直接调用 webHost 的消息接收函数
				webHost.receiveJsMessage(message);
			};
			
			// 实现实例的 subscribeEx()
			me.subscribeEx = function(addFilters, delFilters, token, callback, timeout) {
				me.rpc(null, token, "_subscribe", {add: addFilters, del: delFilters}, callback, timeout);
			};
			
			// 实现实例的 close()
			me.close = function() { 
				me.theLink = null; 
			};

			// 保存连接对象
			me.theLink = webHost;
			// 回调实例方法
			me.onOpen();
		}

		/** 
		 * ============================================================================================================
		 * JsonRpc@HTTP 实现
		 * ============================================================================================================
		*/			
		/**
		 * JsonRpc@HTTP 连接
		 * @param {Object} url 实际上是连接名称，一般为 "http"
		 */ 
		_connectHttp(url) {
			let me = this;

			// 如果连接对象已经存在，则直接返回即可
			if(me.theLink) { me.onOpen(); return; }
			
			// 1.创建ajax对象(不复用)
			let xmlHttp = utils.getXMLHttpRequest(false);
			if(xmlHttp == null) {
				// 回调实例方法
				me.onError({errCode: 500, body: "创建 XMLHttpRequest 对象失败！"});
				return;
			}
			// 设置请求的超时时间
			xmlHttp.timeout = me.rpcTimeout;
			// 设置响应返回的数据格式
			xmlHttp.responseType = "text";
			
			// XMLHttpRequest 状态监听函数
			let onreadystatechange = function() {
				// 如果不是 'loaded' 状态，则不处理
				if (xmlHttp.readyState != 4) { return; }
				// 如果没有正调用的 RPC 请求，则不需要处理接收到的数据
				if (!me.currentRpcMsgId) { return; }
				
				// 返回 RPC 调用结果
				if (xmlHttp.status == 200) {
					me.onMessage(xmlHttp.responseText);
				} else {
					me.onMessage(JSON.stringify({msgId: me.currentRpcMsgId, msgType: MSG_RETURN, errCode: xmlHttp.status, body: xmlHttp.responseText}));
				}
				// 调用完成
				me.currentRpcMsgId = null;
			}

			/**
			 * 实现实例的 send()
			 * @param {Object} target 要调用的目标 URL，如：http(s)://localhost:9404/apis/jsonRpc
			 * @param {Object} message 消息(JSON 格式字符串)
			 * @param {Object} qos 未用
			 */ 
			me.send = function(target, message, qos) {
				// 解析消息，以便进行错误处理
				let msg = JSON.parse(message)
				
				// 重用 XMLHttpRequest 对象
				if(xmlHttp.readyState == 2 || xmlHttp.readyState == 3) {
					let err = "另一个调用尚未完成，不允许发起新的调用！";
					console.log(err);
					if (msg.msgType == MSG_CALL) { me.onMessage(JSON.stringify({msgId: msg.msgId, msgType: MSG_RETURN, errCode: xmlHttp.status, body: err})); }
				    return;
				}
				if(xmlHttp.readyState == 4) {					
					// 如果状态为 DONE(已完成)，则可能要处理一下再复用
					try {
					    xmlHttp.abort();
					} catch (e) {
						let err = "复位并重用 XMLHttpRequest 对象时出错！";
						console.log(err);
						if (msg.msgType == MSG_CALL) { me.onMessage(JSON.stringify({msgId: msg.msgId, msgType: MSG_RETURN, errCode: xmlHttp.status, body: err})); }
					    return;
					}
			    }
				
				// 2.绑定处理请求的地址,true为异步，false为同步
				// GET方式提交把参数加在地址后面?key1:value&key2:value
				xmlHttp.open("POST", target, true);
				// 3.POST提交设置的协议头（GET方式省略）
				// 简单请求进行跨域访问时，XMLHttpRequest 对象会直接将实际请求发送给服务器。简单请求具有如下特点：
				// 1) 只能使用GET、HEAD、POST方法。
				// 2) 请求时不能使用自定义的HTTP Headers。
				// 3) 使用POST方法向服务器发送数据时，Content-Type只能使用application/x-www-form-urlencoded、multipart/form-data或text/plain编码格式。
                // 因为 JsonRpc@HTTP 请求中的 body 是 JSON 格式的参数，所以只能使用 text/plain
				xmlHttp.setRequestHeader("Content-type", "text/plain");
				// 4.绑定状态监听函数
				xmlHttp.onreadystatechange = onreadystatechange;
				// 5.保存当前正在发送的 RPC 消息ID，以便出错时回调
				me.currentRpcMsgId = (msg.msgType == MSG_CALL) ? msg.msgId : null;
				// 6.发送请求
				xmlHttp.send(message);
			}
			
			// 实现实例的 subscribeEx()
			me.subscribeEx = function(addFilters, delFilters, token, callback, timeout) {
				if(callback) { callback({errCode: 500, body: "不支持消息订阅功能!"}); } 
			};
			
			// 实现实例的 close()
			me.close = function() { 
				xmlHttp.abort(); 
				me.theLink = null; 
			}
			
			// 保存连接对象
			me.theLink = xmlHttp;
			// 回调实例方法
			me.onOpen();
		}
		
		/** 
		 * ============================================================================================================
		 * 统一封装各协议实现接口
		 * ============================================================================================================
		*/
		/**
		 * 根据地址协议类型，自动连接到对应的 JsonRpc 服务
		 */
		connect() {
			console.log("JsonRpc 准备连接到 " + this.address + " ...");
			
			// 提取承载协议类型和连接地址
			let [trans, url] = this.address.split("@");
			if(utils.empty(trans) || utils.empty(url)) {
				this.onError({errCode: 400, body: "JsonRpc 地址格式错误！"});
				return;
			}
			
			// 根据载协议类型进行连接
			// JsonRpc@MQTT@WebSocket
			if(trans == "mqtt") { return this._connectMqtt(url); }
			// JsonRpc@WebSocket
			if(trans == "wskt") { return this._connectWebSocket(url); }
			// JsonRpc@WebHost
			if(trans == "webhost") { return this._connectWebHost(url); }
			// JsonRpc@HTTP
			if(trans == "http") { return this._connectHttp(url); }
			
			this.onError({errCode: 400, body: "不支持的 JsonRpc 承载协议！" + trans});
		}
	}

	// 把模块放到全局命名空间中去
	// 为保持兼容性应用程序在加载模块时可以使用如下方式：
	// let JsonRpc = (typeof(global) === undefined) ? window.JsonRpc : global.JsonRpc;
	(function() {
		return (typeof global !== "undefined") ? global :
			(typeof self !== "undefined") ? self :
			(typeof window !== "undefined") ? window : {};
	})()["JsonRpc"] = JsonRpc;
}
